#!/usr/local/bin/python3


##TODO:  Re-write regex to a verbose search for readibility
def readmetrics(metrics_file,fstats=False,fusers=(),itemlimit=0):
	'''Read a metrics file and return a dictionary of user data
	
	Arguments: metrics_file - text file generated from a comms-stor machine by the use of `mboxutil -l > metrics_file`
		
	Returns:  dict
	
	Return Dictionary Structure:  key=username,  key[0]=message store,  key[1]=mailbox size, key[2]=# of messages, key[3]=# of folders
	'''
	import re
	global users
	if fstats:
		flimitfound = set()
	with open(metrics_file,encoding='ISO8859-1') as metrics:		#Silly Solaris uses ISO8859-01 as it's encoding (or at least that's what `mboxutil -l` outputs)
		for line in metrics:
			res = re.search(r'^(\d+)\D+(\d+)\D.+(store\d[abc]?|primary)\W.+\suser/(\w+)\/(.+) \/',line.lstrip())  #need to turn this into a verbose regex for readability
			if res:								#If we received a result
				res = res.groups()			#break out the groups
				currentuser = res[-2]		#the username is second to last in the tuple
				if fstats:
					if currentuser in fusers:
						#print("Found user... {0}".format(currentuser))
						if (int(res[0]) >= int(itemlimit)):
							print("{0} {1} {2}".format(currentuser,res[0],res[-1]))
							flimitfound.add(currentuser)
				msgs = res[0]				# num. messages is the current folder
				size = res[1]				# size of the current folder
				store = res[2]				# message store of the folder
				if (currentuser in users):	#if the user already exists in the dict we want to += for msgs & size, and ++ their folder count
					users[currentuser] = [users[currentuser][0], users[currentuser][1] + int(size), users[currentuser][2] + int(msgs), users[currentuser][3] + 1]
				else:						#for new users to the dict, set the initial values with that of the current folder
					users[currentuser] = [store, int(size), int(msgs), int(1)] #key=username 0=store, 1=size, 2=msgs, 3=folders
	metrics.close()		#we can close the file when we're done
	if fstats:
		return flimitfound
	else:
		return users		#and give the calling function the results

#users = readmetrics(file,True,peeps,itemlimit)

def storedict(dict_file):
	'''store the dictionary in use to a pickle file for quick access in future runs
	
	Arguments: dict_file - output filename
	Returns:   Nothing
	
	'''
	import pickle
	output = open(dict_file,'wb')	#open the output file for writing
	pickle.dump(users, output)		#dump the users dictionary to disk
	output.close					#close the file
	return							#done


def loadmetrics(dict_file):
	'''Load a metrics dictionary from disk
	
	Arguments: dict_file - pickle file on disk containing user metrics data.
	Returns: dict - user metrics data
	'''
	global users
	import pickle
	pkl_file = open(dict_file,'rb')	#open the pickle file
	users = pickle.load(pkl_file)	#load it into the users dict
	pkl_file.close()				#close the file
	return users					#pass the dict back to the calling function


def getuser(usr):
	'''get information of a specific user
	
	Arguments: usr - name of user to retrieve data for
	Returns:   tuple - of user data
	'''
	if (usr in users):	#if the user is in the dictionary, return the data
		return (usr, users[usr][0], convert_size(users[usr][1]*1024), users[usr][2], users[usr][3])  #this is human readable size
		#awk -F',' '{print $1 " - " $3 " - " $4 " emails - " $5 " folders"}' more verbose
	else:				#otherwise, return None
		return (usr,"NOTFOUND","NOTFOUND","NOTFOUND","NOTFOUND")


def getusers(userlist):
	'''retrieves and returns data for a list of users
	
	Arguments - userlist[] - a list of usernames
	Returns   - list of tuples of user data
	'''
	results = []						#create an empty list
	for usr in userlist:				#step through the userlist
		results.append(getuser(usr))	#append the user data tuple to results[]
	
	return results						#return the list

		
def printallusers():
	'''prints data for all users to the screen
	
	Arguments: none
	Returns: none
	'''
	for dude in users:	#for each person in the dictionary, print their data:  user,store,size,#msgs,#folders
		print('{0},{1},{2},{3},{4}'.format(dude,users[dude][0],users[dude][1],users[dude][2],users[dude][3]))


def printuser(usr_list):
	'''print data for a single user
	
	Arguments: usr - the name of the user to print data for
	Returns:   nothing - data is printed to screen
	'''
	
	for usr in usr_list:
		data = getuser(usr)	#use getuser() to grab data for the user
		if (data):			#if there is any data, print it!   #username,store,size,#msgs,#folders
			print('{0},{1},{2},{3},{4}'.format(data[0],data[1],data[2],data[3],data[4]))


def runstats(usr_list):
	'''gather running stats for the list of users presented on commandline
	
	Arguments: usr_list 
	'''
	peeps = len(usr_list)		#total number of users is equal to the number of keys in the list
	print('Total Users - {0}'.format(peeps))	#Print it!
	size = 0				#set the size to 0
	msgs = 0				#set the # of messages to 0
	fldrs = 0				#set the # of folders to 0
	for dude in usr_list:		#step through the dictionary
		if (dude in users):
			size  = size  + int(users[dude][1] * 1024)	#add the size of the user's mailbox (mult by 1024 to convert Kb to b for convert_size) to previous size 
			msgs  = msgs  + int(users[dude][2])			#add to the message count
			fldrs = fldrs + int(users[dude][3])			#add to the folder count
	#Lost of stuff getting printed here, read the strings to see what
	print('Total Size of All Messages - {0}'.format(convert_size(size)))
	print('Total Messages - {0}'.format(msgs))
	print('Total Folders - {0}'.format(fldrs))
	print('Average size per user - {0}'.format(convert_size(float(size)/peeps)))		#use convert_size to convert bytes to a readable number
	print('Average size per message - {0}'.format(convert_size(float(size)/msgs)))
	print('Average size per folder - {0}'.format(convert_size(float(size)/fldrs)))
	print('Average Messages per Folder - {0:.2f}'.format(float(msgs)/fldrs))
	print('Average Messages per User - {0:.2f}'.format(float(msgs)/peeps))
	print('Average Folders per User - {0:.2f}'.format(float(fldrs)/peeps))



def gatherfstatusers(itemlimit):
	'''Parse through the metrics file to determine which users may have more than itemlimit worth of messages in a single folder.  These are basically candidates to check
	
	Arguments: itemlimit (passed via command line
	
	Returns: set
	
	'''
	peeps = set()
	for dude in users:
		itemlimit = int(itemlimit)
		items = int(users[dude][2])
		if (items >= itemlimit):
			#print('{0} has {1} items'.format(dude,users[dude][2]))
			peeps.add(dude)
	return peeps
	


def printfstats(itemlimit):
	global metricsfile
	peeps = gatherfstatusers(itemlimit)
	found = readmetrics(metricsfile,True,peeps,itemlimit)
	#printuser(found)






def printstats():
	'''print statistics for the metrics file being used
		
	Arguments: none
	Returns:   none
	
	'''
	peeps = len(users)		#total number of users is equal to the number of keys in the dict
	print('{0} - Total Users'.format(peeps))	#Print it!
	size = 0				#set the size to 0
	msgs = 0				#set the # of messages to 0
	fldrs = 0				#set the # of folders to 0
	for dude in users:		#step through the dictionary
		size  = size  + int(users[dude][1] * 1024)	#add the size of the user's mailbox (mult by 1024 to convert Kb to b for convert_size) to previous size 
		msgs  = msgs  + int(users[dude][2])			#add to the message count
		fldrs = fldrs + int(users[dude][3])			#add to the folder count
	#Lost of stuff getting printed here, read the strings to see what
	print('{0} - Total Size of All Messages'.format(convert_size(size)))
	print('{0} - Total Messages'.format(msgs))
	print('{0} - Total Folders'.format(fldrs))
	print('{0} - Average size per user'.format(convert_size(float(size)/peeps)))		#use convert_size to convert bytes to a readable number
	print('{0} - Average size per message'.format(convert_size(float(size)/msgs)))
	print('{0} - Average size per folder'.format(convert_size(float(size)/fldrs)))
	print('{0:.2f} - Average Messages per Folder'.format(float(msgs)/fldrs))
	print('{0:.2f} - Average Messages per User'.format(float(msgs)/peeps))
	print('{0:.2f} - Average Folders per User'.format(float(fldrs)/peeps))


def convert_size(size):
	'''Convert a file size to human-readable form.
	
    Keyword arguments:
    size -- file size in bytes
	
    Returns: string
    '''
	#based on humansize.py from http://diveintopython3.org/
	
	values = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
	if size < 0:
		raise ValueError('number must be non-negative')
	for label in values:
		size /= 1024
		if size < 1024:
			return '{0:.2f} {1}'.format(size,label)
	raise ValueError('number too large')


def loadfile(filename,pickle=True):
	'''Load either an existing pickle file or the file provided.
	
	Arguments: filename - name of metrics file
	           pickle   - Boolean for whether or not to use pickles
	Returns:   dict		- returns the users dictionary
	'''
	
	pick = filename + '.pkl'
	#load or read metrics/pickle file (using --file if provided)		
	if pickle:														#If we're using pickles
		import glob
		if glob.glob(pick):											#and the pickle file exists
			print('o Using existing pickle file: {0}'.format(pick))	#tell the user we're using it
			users = loadmetrics(pick)								#load the existing pickle file 
		else:														#otherwise...
			print('o Reading metrics file: {0}'.format(filename))	
			users = readmetrics(filename)							#read the file the user told us about
			storedict(pick)											#and store it as a pickle file for next time
			print('o Stored metrics in pickle: {0}'.format(pick))	
	else:															#If the user doesn't want to use pickles...
		print('o Reading metrics file: {0}'.format(filename))		
		users = readmetrics(filename)								#read the file the user told us about
		
	return users													#return the user dict to the calling function


def cli_init():
	import argparse
	parser = argparse.ArgumentParser(description='Comms Suite Libraries for Metrics Analysis')
	parser.add_argument('-f','--file', dest='file',default='metrics.txt',help='name of metrics file (default: metrics.txt)')
	parser.add_argument('-u','--user',dest='user',nargs='+',help='Show details for user(s)')
	parser.add_argument('-r','--userstats',action='store_true',help='Show statistics for only the users associated with -u')
	parser.add_argument('-s','--stats',action='store_true',help='Show statistics')
	parser.add_argument('-l','--list',action='store_true',help='List details for all users')
	parser.add_argument('-n','--no-pickle',action='store_true',help='Do not read from or write to pickle files (slower)')
	parser.add_argument('--itemlimit',dest='itemlimit',help='Show folders with greater then specified number of items')
	args = parser.parse_args()
	
	global users
	global metricsfile 
	metricsfile = args.file

	#load our metrics file, we want to tell loadfile() whether to use pickles or not
	if args.no_pickle:
		users = loadfile(args.file,False)	#load metrics without reading from or writing to pickles
	else:
		users = loadfile(args.file)    #utilize pickles (default)
	
	#if a username is given with --user
	if args.user:
		printuser(args.user)	#print that sucker out!
		if args.userstats:
			runstats(args.user)
	
	#if the user requests a full printout of all users:  --list
	if args.list:		
		printallusers()			#so by golly we'll print them all out
	
	#if statistics are requested using --stats
	if args.stats:
		printstats()			#give the user what they request
		
	#if folderstats are requested using --itemlimit
	if args.itemlimit:
		printfstats(args.itemlimit)			#show folders with X number of items or more

	
if __name__ == '__main__':
	users = {}		#create a global dictionary to hold user data
	cli_init()		#if we run from the command line, let's go for it
